ついカッとなって、Pythonで時刻文字列のタイムゾーン変換をまとめてみた
はじめに
こんにちは、平野です。
PythonでUTCからJSTへの時刻の変換を行いたいと思った時、みなさんの中で必勝法は固まっているでしょうか? 私はWebで調べて、なんかいろんなパッケージが出てくるなー、と思いつつ貼り付けをしていました。 そんな状態がしばらく続いて、理解できていない自分に嫌気が差したので、 自分の中の必勝法を確立するべくいくつかのやり方を試してみました。
検証環境
- macOS High Sierra バージョン10.13.6
- Python 3.7.3
題材
UTCの時刻文字列をJSTの時刻文字列に変換します。
標準ライブラリだけを使う
datetime
datetimeは日付・時刻を扱うPythonの標準ライブラリです。 標準ライブラリでできることは標準ライブラリでやる、 というのがプログラムを書く上ではかなり重要かな、と思っております。 何はなくとも、まずはこのライブラリに当たるべきです。
datetime
で気をつけるべき点としてawareとnaiveという概念があります。
公式ドキュメントから
aware オブジェクトは他の aware オブジェクトとの相対関係を把握出来るように、タイムゾーンや夏時間の情報のような、アルゴリズム的で政治的な適用可能な時間調節に関する知識を持っています。aware オブジェクトは解釈の余地のない特定の実時刻を表現するのに利用されます [1]。
naive オブジェクトには他の日付時刻オブジェクトとの相対関係を把握するのに足る情報が含まれません。あるプログラム内の数字がメートルを表わしているのか、マイルなのか、それとも質量なのかがプログラムによって異なるように、naive オブジェクトが協定世界時 (UTC) なのか、現地時間なのか、それとも他のタイムゾーンなのかはそのプログラムに依存します。Naive オブジェクトはいくつかの現実的な側面を無視してしまうというコストを無視すれば、簡単に理解でき、うまく利用することができます。
「aware」はタイムゾーンも考慮した"絶対的なあるタイミング"を指定し、 「naive」はタイムゾーンを指定しないと"絶対的なあるタイミング"が特定できない表現だと解釈できます1。 言葉にするとややこしいですが、そんなに難しい概念ではないですね。
datetimeだけでUTC->JST変換
なんということはなく、datetime
だけでも目的の変換は達成できました。
datetime
だけを使ってUTCからJSTへの変換を行うコードは以下になります。
なお、datetime
だけをimportして、datetime.datetime.strftime
などを冗長に書いているのは、
「datetimeモジュール」の中に「datetimeクラス」がある、というややこしい事情を意識するために敢えてやっています。
必要に応じて適宜importして頂ければと思います。
import datetime def utc_to_jst(timestamp_utc): datetime_utc = datetime.datetime.strptime(timestamp_utc + "+0000", "%Y-%m-%d %H:%M:%S.%f%z") datetime_jst = datetime_utc.astimezone(datetime.timezone(datetime.timedelta(hours=+9))) timestamp_jst = datetime.datetime.strftime(datetime_jst, '%Y-%m-%d %H:%M:%S.%f') return timestamp_jst input = '2019-04-09 10:57:13.015' print("[UTC] " + input) print("[JST] " + utc_to_jst(input)) # ==> "[UTC] 2019-04-09 10:57:13.015" # ==> "[JST] 2019-04-09 19:57:13.015000"
datetime.datetime.strptime
で文字列をパースしてオブジェクトを作ります。
%Y-%m-%d
などの表記は煩わしく感じますが、
変に曖昧なパースをされるよりも、正確に記述した方が安心感があります。
ここでは文字列として+0000
を後置させ、%z
でパースすることでUTCとしてawareなオブジェクトを作成しています。
次にJSTに変換する部分ですが、ポイントはastimezone
の引数をどう作成するかです。
datetime
だけを使う場合はdatetime.timedelta(hours=+9)
とすればOKです。
ここでpytz
というライブラリを使っているケースなども見受けられますが、標準ライブラリだけでも実現できます。
タイムゾーン作成の別の方法
タイムゾーンのオブジェクトを作成する際に、hours=+9
のような表記ではなく、
JST
やAsia/Tokyo
などの表記でタイムゾーンを表現したいこともあります。
しかしdatetime
はそれらの表記とUTCからの相対時間の関係を把握していないため、
datetime
だけで実現することはできません。
そこでdateutil
パッケージも少しだけ使ってみます。
ただし、以下の記事によれば、JST
やAsia/Tokyo
などの表記よりも相対時間で表した方が良いようです。
タイムゾーン呪いの書#"JST": Jerusalem Standard Time?
こちらの記事、非常に勉強になって素晴らしいです。
ただ、読めば読むほどタイムゾーンも闇が深くて怖いです。。。
dateutil
powerful extensions to datetime
と書かれている通り、datetime
を拡張させたものです。
標準ライブラリではないのでどこでも使えるとは限りませんが、
少なくともAWS LambdaのPythonでは標準で使うことができるようです。
datetimeとdateutilでUTC->JST変換
dateutil
も使用して、タイムゾーン指定にAsia/Tokyo
を使用した例です。
import datetime from dateutil.tz import gettz def utc_to_jst(timestamp_utc): datetime_utc = datetime.datetime.strptime(timestamp_utc + "+0000", "%Y-%m-%d %H:%M:%S.%f%z") datetime_jst = datetime_utc.astimezone(gettz('Asia/Tokyo')) timestamp_jst = datetime.datetime.strftime(datetime_jst, '%Y-%m-%d %H:%M:%S.%f') return timestamp_jst input = '2019-04-09 10:57:13.015' print("[UTC] " + input) print("[JST] " + utc_to_jst(input)) # ==> "[UTC] 2019-04-09 10:57:13.015" # ==> "[JST] 2019-04-09 19:57:13.015000"
タイムゾーンの取得の部分が
dateutil.tz.gettz('Asia/Tokyo')
に変わっています。
JST
という文字列でも同様の動作になりましたが、先ほどの記事を見る限り、使わない方が良さそうです。
なお、strptime
している部分も+0000
ではなくUTC
などと書きたかったのですが、
なぜか上手くいきませんでした。
%Z
は、JST
は日本標準時としてパースしてくれたのですが、
UTC
はnaiveなオブジェクトになってしまうようなので+0000
のままにしています。
使わなかったメソッド・パッケージ
datautil.parser
datautil.parser
というメソッドを使うと%Y-%m-%d
などを使わなくても、
かなり"いい感じ"に文字列をパースしてくれます。
書きなぐりのプログラムには向いていますが、 このくらいの厳密さはあってもいいと思うので、 今回は特に取り上げませんでした。
pytz
pytzは、introductionの先頭に
pytz brings the Olson tz database into Python.
と書かれている通り、Olsonタイムゾーンの定義を使用するためのパッケージです。 Olsonタイムゾーンについてはtz databaseを参照して下さい。
前述のように、dateutil
を使ってもAsia/Tokyo
などの文字列からタイムゾーンの情報を得ることができているので、
少なくとも今回のようなUTC->JSTの変換のような用途ではpytz
は不要なようです。
なお、以下の記事にpytz
が推奨されると書いてありますが、Python3.3と古く、
Python3.7の同一URLのページにはそのような記載は見当たりません。
Pythonの日付処理とTimeZone
まとめ
Python3.7で、ただの文字列である時刻をUTCとみなして、JSTの時間に変更するやり方についてまとめてみました。
Webを検索すると主にdatetime
とdateutil
とpytz
の3つのパッケージが登場していて、
それぞれ少しずつやり方も違うので混乱していたのですが、
自分なりには納得できる形でまとめることができました。
調べてみた結果、datetime
だけで変換ができるということが一番の収穫でした。
今回のような変換に限らず、基本的にはdatetime
だけでできる処理のやり方を模索し、
それがダメだった(り、あまりにもやりづらかったりした)場合に他のパッケージに当たるのが良さそうです。
日本では意識することが少ないですが、タイムゾーンも文字コード並みに闇が深そうなので、 「完全に理解した」の精神の下、今の理解で全て対応できると思い込むことで、 あまり深淵には近づきたくないという気持ちでいっぱいです。
以上、誰かの参考になれば幸いです。
- "絶対的なあるタイミング"とは、ドキュメントの脚注にも書かれている通り、相対性理論を考慮しない前提に依っている。特殊相対性理論を考慮した場合、異なる慣性系における「同時」は(日常の感覚では)定義できない。あれ、もしかしてこの注釈要らない? ↩